#include <glib.h>
#include <dlfcn.h>
#include "pytextparser.h"
#include "structmember.h"

static PyObject* TextParserError = NULL;

static void TextParser_dealloc(TextParser* self)
{
  self->parser_free(self->parser);
  self->ob_type->tp_free((PyObject*)self);
}

static PyObject* TextParser_new(PyTypeObject* type,
                                PyObject*     args,
                                PyObject*     kwds)
{
  TextParser* self = (TextParser*)type->tp_alloc(type, 0);
  if (self == NULL)
    return NULL;
  self->dlhandle     = NULL;
  self->parser_new   = NULL;
  self->parser_free  = NULL;
  self->parser_run   = NULL;
  self->parser_error = NULL;
  self->callback     = NULL;
  return (PyObject*)self;
}

long dlget(TextParser* parser, const char* name)
{
  void* ptr = dlsym(parser->dlhandle, name);
  if (!ptr) {
    PyErr_SetString(TextParserError, dlerror());
    return (long)NULL;
  }
  return (long)ptr;
}

static int TextParser_init(TextParser* self, PyObject* args, PyObject* kwds)
{
  const char* filename = NULL;

  if (!PyArg_ParseTuple(args, "s", &filename))
    return -1;

  // Attempt to load the module given as the first argument.
  self->dlhandle = dlopen(filename, RTLD_LAZY);
  if (!self->dlhandle) {
    PyErr_SetString(TextParserError, dlerror());
    return -1;
  }

  // Find the function symbols of the parser.
  // Note that dlsym returns void*, which according to the ISO C standard
  // can not be cast into a function pointer, which would make dlsym useless. 
  // Thus the weird double cast via "long".
  self->parser_new = (ParserNewFunc)dlget(self, "parser_new");
  if (!self->parser_new)
    return -1;
  self->parser_free = (ParserFreeFunc)dlget(self, "parser_free");
  if (!self->parser_free)
    return -1;
  self->parser_run = (ParserRunFunc)dlget(self, "parser_run");
  if (!self->parser_run)
    return -1;
  self->parser_stop = (ParserFreeFunc)dlget(self, "parser_stop");
  if (!self->parser_stop)
    return -1;
  self->parser_error = (ParserErrorFunc)dlget(self, "parser_error");
  if (!self->parser_error)
    return -1;

  // Initialize the parser.
  self->parser = ((ParserNewFunc)self->parser_new)();

  return 0;
}

static void my_callback(Parser* parser, int type, const char* name, void* data)
{
  TextParser* self    = (TextParser*)data;
  PyObject*   arglist = Py_BuildValue("(is)", type, name);
  PyObject*   result  = PyEval_CallObject(self->callback, arglist);
  Py_DECREF(arglist);
  if (!result) {
    self->parser_stop(parser);
    return;
  }
  Py_DECREF(result);
}

static PyObject* TextParser_parse(TextParser* self, PyObject* args)
{
  const char* string   = NULL;
  PyObject*   callback = NULL;
  int         result   = 0;

  if (!PyArg_ParseTuple(args, "sO:parse", &string, &callback))
    return NULL;
  if (!PyCallable_Check(callback)) {
    PyErr_SetString(PyExc_TypeError, "parameter must be callable");
    return NULL;
  }
  Py_XINCREF(callback);
  Py_XDECREF(self->callback);
  self->callback = callback;

  result = self->parser_run(self->parser,
                            string,
                            my_callback,
                            self);
  if (PyErr_Occurred()) 
    return NULL;
  if (result != 0) {
    PyErr_SetString(TextParserError, self->parser_error(self->parser));
    return NULL;
  }

  Py_INCREF(Py_None);
  return Py_None;
}


static PyMemberDef TextParser_members[] = {
    {NULL}
};

static PyMethodDef TextParser_methods[] = {
    {"parse",
     (PyCFunction)TextParser_parse,
     METH_VARARGS,
     "Parses the given string, passing tokens to the given callback."},
    {NULL}
};

static PyTypeObject TextParserType = {
    PyObject_HEAD_INIT(NULL)
    0,                              /*ob_size*/
    "pytextparser.TextParser",      /*tp_name*/
    sizeof(TextParser),             /*tp_basicsize*/
    0,                              /*tp_itemsize*/
    (destructor)TextParser_dealloc, /*tp_dealloc*/
    0,                              /*tp_print*/
    0,                              /*tp_getattr*/
    0,                              /*tp_setattr*/
    0,                              /*tp_compare*/
    0,                              /*tp_repr*/
    0,                              /*tp_as_number*/
    0,                              /*tp_as_sequence*/
    0,                              /*tp_as_mapping*/
    0,                              /*tp_hash */
    0,                              /*tp_call*/
    0,                              /*tp_str*/
    0,                              /*tp_getattro*/
    0,                              /*tp_setattro*/
    0,                              /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
    "TextParser objects",           /* tp_doc */
    0,                              /* tp_traverse */
    0,                              /* tp_clear */
    0,                              /* tp_richcompare */
    0,                              /* tp_weaklistoffset */
    0,                              /* tp_iter */
    0,                              /* tp_iternext */
    TextParser_methods,             /* tp_methods */
    TextParser_members,             /* tp_members */
    0,                              /* tp_getset */
    0,                              /* tp_base */
    0,                              /* tp_dict */
    0,                              /* tp_descr_get */
    0,                              /* tp_descr_set */
    0,                              /* tp_dictoffset */
    (initproc)TextParser_init,      /* tp_init */
    0,                              /* tp_alloc */
    TextParser_new,                 /* tp_new */
};


/*******************************************************************
 * Module functions.
 *******************************************************************/
static PyMethodDef TextParserMethods[] = {
    {NULL, NULL, 0, NULL}
};


PyMODINIT_FUNC
initpytextparser(void)
{
  PyObject* m = NULL;

  if (PyType_Ready(&TextParserType) < 0)
    return;

  m = Py_InitModule("pytextparser", TextParserMethods);
  if (m == NULL)
    return;

  // Define the TextParser type.
  Py_INCREF(&TextParserType);
  PyModule_AddObject(m, "TextParser", (PyObject*)&TextParserType);

  // Define our own exception type.
  TextParserError = PyErr_NewException("pytextparser.error", NULL, NULL);
  Py_INCREF(TextParserError);
  PyModule_AddObject(m, "error", TextParserError);
}
